{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "de765f16-341b-4dc3-b59d-b16437c7e050",
   "metadata": {},
   "source": [
    "# LanceDB RAG\n",
    "\n",
    "Similar RAG use case as `5-RAG-Structurizr-Language-QA.ipynb` to use LanceDB instead of DocArrayInMemorySearch"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "aa827cda-eb15-4767-9f8a-92d18bc9840c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Name: lancedb\n",
      "Version: 0.16.0\n",
      "Summary: lancedb\n",
      "Home-page: \n",
      "Author: \n",
      "Author-email: LanceDB Devs <dev@lancedb.com>\n",
      "License: Apache-2.0\n",
      "Location: /opt/conda/lib/python3.11/site-packages\n",
      "Requires: deprecation, nest-asyncio, overrides, packaging, pydantic, pylance, tqdm\n",
      "Required-by: \n"
     ]
    }
   ],
   "source": [
    "!pip show lancedb"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "13a9da9e-5b71-46c5-b392-edd84f06ed23",
   "metadata": {},
   "source": [
    "## Create LanceDB"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "7747115f-b0e9-4214-a440-06f0622b0e52",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 952 ms, sys: 92 ms, total: 1.04 s\n",
      "Wall time: 545 ms\n"
     ]
    }
   ],
   "source": [
    "%%time\n",
    "import lancedb\n",
    "\n",
    "db = lancedb.connect(\"lancedb\")\n",
    "\n",
    "## a folder \"lancedb\" will be created automatically\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8942a4ca-a0e2-4e50-857b-171d11a4d047",
   "metadata": {},
   "source": [
    "## Create Embedding Model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "0c78cbd2-69b8-4b8c-9578-9c6a07c246bf",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_ollama import OllamaEmbeddings\n",
    "\n",
    "ollama_url = \"http://ollama:11434\"\n",
    "\n",
    "model_name = \"nomic-embed-text:v1.5\"\n",
    "\n",
    "embeddings = OllamaEmbeddings(\n",
    "    base_url = ollama_url,\n",
    "    model=model_name\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1dee0bcc-2a8f-40bf-b789-fcc67b5ac814",
   "metadata": {},
   "source": [
    "## Create table"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "f7255077-07b3-40f4-952a-0ba904d3e08c",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[2024-11-17T12:30:55Z WARN  lance::dataset] No existing dataset at /home/jovyan/work/lancedb/language.lance, it will be created\n"
     ]
    }
   ],
   "source": [
    "# import pyarrow as pa\n",
    "# schema = pa.schema(\n",
    "#     [\n",
    "#         pa.field(\"vector\", pa.list_(pa.float32(), list_size=768)),\n",
    "#         pa.field(\"text\", pa.string())\n",
    "#     ]\n",
    "# )\n",
    "# table = db.create_table(\"structurizr\", schema=schema, mode = \"overwrite\")\n",
    "## a folder \"lancedb/structurizr.lance\" will be created automatically\n",
    "\n",
    "# table to store language reference\n",
    "language = db.create_table(\"language\", data = [{\"vector\":embeddings.embed_query(\"language\"), \"text\":\"language\",\"metadata\":{\"source\":\"test\"}}], mode = \"overwrite\")\n",
    "## a folder \"lancedb/language.lance\" will be created automatically\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "b24f2cde-14e3-4fb8-9ade-05b97c0beee3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "                                              vector      text  \\\n",
      "0  [0.011897956, 0.017863832, -0.134587, -0.05154...  language   \n",
      "\n",
      "             metadata  \n",
      "0  {'source': 'test'}  \n"
     ]
    }
   ],
   "source": [
    "# Get the updated table as a pandas DataFrame\n",
    "print(language.to_pandas())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "00d41e2e-16dd-4bfb-8af4-857d61e2c77e",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[2024-11-17T12:30:55Z WARN  lance::dataset] No existing dataset at /home/jovyan/work/lancedb/example.lance, it will be created\n"
     ]
    }
   ],
   "source": [
    "# table to store example\n",
    "example = db.create_table(\"example\", data = [{\"vector\":embeddings.embed_query(\"example\"), \"text\":\"example\",\"metadata\":{\"source\":\"test\"}}], mode = \"overwrite\")\n",
    "## a folder \"lancedb/example.lance\" will be created automatically"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "039a827a-595a-4ecc-ae4c-9d4a0ae25397",
   "metadata": {},
   "source": [
    "## Embeb text"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "b3bef9c4-29f5-473a-a400-d425ecc3b92f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Make up some fact to demonstrate RAG on knowledge base\n",
    "\n",
    "knowledge = \"Some famous companies such as ABC and XYZ are using Structurizr DSL for software architecture modeling and documentation.\"\n",
    "\n",
    "example.add(data = [{\"vector\":embeddings.embed_query(knowledge), \"text\":knowledge,\"metadata\":{\"source\":\"test1\"}}])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "3d426994-e34d-48d6-b994-bc62f8845b14",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 4.25 ms, sys: 4.99 ms, total: 9.24 ms\n",
      "Wall time: 76.2 ms\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>vector</th>\n",
       "      <th>text</th>\n",
       "      <th>metadata</th>\n",
       "      <th>_distance</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>[-0.013173782, 0.06878591, -0.16326903, -0.024...</td>\n",
       "      <td>Some famous companies such as ABC and XYZ are ...</td>\n",
       "      <td>{'source': 'test1'}</td>\n",
       "      <td>0.829875</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>[0.048995268, 0.041218117, -0.17567171, -0.026...</td>\n",
       "      <td>example</td>\n",
       "      <td>{'source': 'test'}</td>\n",
       "      <td>1.159160</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                              vector  \\\n",
       "0  [-0.013173782, 0.06878591, -0.16326903, -0.024...   \n",
       "1  [0.048995268, 0.041218117, -0.17567171, -0.026...   \n",
       "\n",
       "                                                text             metadata  \\\n",
       "0  Some famous companies such as ABC and XYZ are ...  {'source': 'test1'}   \n",
       "1                                            example   {'source': 'test'}   \n",
       "\n",
       "   _distance  \n",
       "0   0.829875  \n",
       "1   1.159160  "
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%%time\n",
    "\n",
    "example.search(embeddings.embed_query(\"Company ABC\")) \\\n",
    "    .limit(5) \\\n",
    "    .nprobes(20) \\\n",
    "    .refine_factor(10) \\\n",
    "    .to_pandas()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4b5ecd71-1e28-4f73-8160-b59e889a2a9d",
   "metadata": {},
   "source": [
    "## Create vectorstore backed by LanceDB\n",
    "\n",
    "https://lancedb.github.io/lancedb/search/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "5cc644e6-61a2-4df7-b363-816f0db49856",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 3.77 ms, sys: 981 µs, total: 4.75 ms\n",
      "Wall time: 6.77 ms\n"
     ]
    }
   ],
   "source": [
    "%%time\n",
    "\n",
    "# embed documents into vectorstore\n",
    "from langchain_community.vectorstores import LanceDB\n",
    "\n",
    "# vectorstore = LanceDB.from_documents(docs,embeddings,connection=table)\n",
    "# example_vectorstore = LanceDB(example, embeddings)\n",
    "# language_vectorstore = LanceDB(language, embeddings)\n",
    "\n",
    "db_url = \"lancedb\" # url of db you created\n",
    "# api_key = \"xxxxx\" # your API key\n",
    "# region=\"us-east-1-dev\"  # your selected region\n",
    "\n",
    "language_vectorstore = LanceDB(\n",
    "    uri=db_url,\n",
    "    # api_key=api_key,\n",
    "    # region=region,\n",
    "    embedding=embeddings,\n",
    "    table_name='language'\n",
    "    )\n",
    "\n",
    "example_vectorstore = LanceDB(\n",
    "    uri=db_url,\n",
    "    # api_key=api_key,\n",
    "    # region=region,\n",
    "    embedding=embeddings,\n",
    "    table_name='example'\n",
    "    )\n",
    "\n",
    "# add texts to vectorstore\n",
    "# vectorstore.add_texts(['text1', 'text2'])\n",
    "# result = vectorstore.similarity_search('text1')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5a6b0217-d3b2-4b2c-9544-162e3ef40495",
   "metadata": {},
   "source": [
    "## Similarity search in vectorstore"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "312e95d1-141e-4f88-b109-362392f4d4e4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "*******\n",
      "Some famous companies such as ABC and XYZ are using Structurizr DSL for software architecture modeling and documentation.\n",
      "\n",
      "*******\n",
      "example\n",
      "\n",
      "CPU times: user 6.56 ms, sys: 1.05 ms, total: 7.61 ms\n",
      "Wall time: 60.1 ms\n"
     ]
    }
   ],
   "source": [
    "%%time\n",
    "\n",
    "result = example_vectorstore.similarity_search(\"Company ABC\")\n",
    "for doc in result:\n",
    "    print(f'*******\\n{doc.page_content}\\n', flush=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a0fb636c-7ef7-4f59-8ea0-68dccba066c4",
   "metadata": {},
   "source": [
    "## Create Loader to load language-reference.md from local file system"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "ab3296e7-e2ab-4026-bd41-49d6b0b25230",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 1.27 s, sys: 123 ms, total: 1.39 s\n",
      "Wall time: 2.03 s\n"
     ]
    }
   ],
   "source": [
    "%%time\n",
    "\n",
    "from langchain_community.document_loaders import UnstructuredMarkdownLoader\n",
    "markdownLoader = UnstructuredMarkdownLoader(\"structurizr/dsl/docs/language-reference.md\")\n",
    "docs = markdownLoader.load()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "03f6a651-e1d1-4257-9f04-80abc5165b4d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 7.08 ms, sys: 1.01 ms, total: 8.09 ms\n",
      "Wall time: 9.9 ms\n"
     ]
    }
   ],
   "source": [
    "%%time\n",
    "from langchain.text_splitter import MarkdownTextSplitter\n",
    "\n",
    "text_splitter = MarkdownTextSplitter(chunk_size=200, chunk_overlap=50)\n",
    "\n",
    "texts = text_splitter.split_documents(docs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "48d471b6-140f-4bd7-8de8-6c4c7e31105b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Document(metadata={'source': 'structurizr/dsl/docs/language-reference.md'}, page_content='The Structurizr DSL provides a way to define a software architecture model (based upon the C4 model) as text, using a domain specific language (DSL). The DSL is rendering tool independent, and to')"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "texts[1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "fe8ed53d-18fd-473a-bb4f-6ff79d994b76",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 114 ms, sys: 17.4 ms, total: 132 ms\n",
      "Wall time: 1min 27s\n"
     ]
    }
   ],
   "source": [
    "%%time\n",
    "\n",
    "result = language_vectorstore.add_documents(texts)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "490ef88d-65fc-4f4e-b29a-1feef185815b",
   "metadata": {},
   "source": [
    "## Vector Search"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "02b3f72b-a7e1-48f9-8a08-e61399644775",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "In addition, workspaces are subject to the following rules:\n",
      "\n",
      "workspace\n",
      "\n",
      "workspace is the top level language construct, and the wrapper for the model and views. A workspace can optionally be given a name and description.\n",
      "\n",
      "workspace [name] [description] { ... }\n",
      "\n",
      "workspace [name] [description] { ... }\n",
      "\n",
      "A workspace can also extend another workspace, to add more elements, relationships, views, etc to it.\n",
      "\n",
      "workspace extends <file|url> { ... }\n",
      "\n",
      "workspace, all of the identifiers defined in that workspace are available to use in the extended workspace.\n",
      "\n",
      "CPU times: user 2.98 ms, sys: 5.92 ms, total: 8.9 ms\n",
      "Wall time: 81.5 ms\n"
     ]
    }
   ],
   "source": [
    "%%time\n",
    "\n",
    "results = language_vectorstore.similarity_search('workspace')\n",
    "\n",
    "for doc in results:\n",
    "    print(f'{doc.page_content}\\n', flush=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "203f97bd-a8a9-4643-84e6-536707688e9e",
   "metadata": {},
   "source": [
    "## Create Retriever"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "4bb5dec3-ad58-4109-880e-683851505f16",
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "from langchain_core.runnables import RunnableParallel, RunnablePassthrough\n",
    "\n",
    "retrieval = RunnableParallel(\n",
    "    {\"context\": language_vectorstore.as_retriever(), \"input\": RunnablePassthrough()}\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "549b614e-9ccc-4290-8245-bbbe848bfce9",
   "metadata": {},
   "source": [
    "## Create Prompt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "feed82a2-403c-4405-9b16-509d99a4d300",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "\n",
    "prompt = ChatPromptTemplate.from_messages([\n",
    "    (\"system\", \"You are a software architect with knowledage in structurizr helping software architecture modeling and documenation in the following context: {context}\"),\n",
    "    (\"user\", \"{input}\")\n",
    "])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "51f8c980-942c-469d-92cd-9e3b9f59e2fd",
   "metadata": {},
   "source": [
    "## Create LLM for QA"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "c938caf6-a794-46c8-805f-7be76c52c9a6",
   "metadata": {},
   "outputs": [],
   "source": [
    "ollama_url = \"http://ollama:11434\"\n",
    "# ollama_url = \"http://localhost:11434\"\n",
    "\n",
    "model_name = \"mistral:v0.3\"\n",
    "\n",
    "from langchain_ollama import OllamaLLM\n",
    "\n",
    "llm = OllamaLLM(base_url=ollama_url,model=model_name)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b2d806f4-cb5c-410b-b2f8-fe646dafb996",
   "metadata": {},
   "source": [
    "## Create OutputParser"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "11ca69e9-1828-44ff-8da7-520eab415f3c",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.output_parsers import StrOutputParser\n",
    "\n",
    "output_parser = StrOutputParser()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "56b930e4-e322-4f6d-8689-4061a3c85466",
   "metadata": {},
   "source": [
    "## Create Chain"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "203fbd64-3588-426b-8d69-3647e0b1bcbb",
   "metadata": {},
   "outputs": [],
   "source": [
    "chain = retrieval | prompt | llm | output_parser"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9c67bd1c-3a52-4258-8ea4-63c3461d3570",
   "metadata": {},
   "source": [
    "## Invoke Chain with keywords"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "0d65c649-44ed-47c2-a717-2309f4b7c4d8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " In Structurizr, a workspace is the top-level language construct and serves as a wrapper for the model and views. It can optionally have a name and description. Here's an example of defining a workspace:\n",
      "\n",
      "```\n",
      "workspace My Application {\n",
      "  This is an application for managing tasks.\n",
      "\n",
      "  <!-- Your models and views go here -->\n",
      "}\n",
      "```\n",
      "\n",
      "A workspace can also be extended to include additional elements, relationships, views, etc. To extend another workspace, you can use the `extends` keyword:\n",
      "\n",
      "```\n",
      "workspace My Application Extended extends common_elements.md {\n",
      "  <!-- Your specific models and views go here -->\n",
      "}\n",
      "```\n",
      "\n",
      "In this case, all identifiers defined in the extended workspace (common_elements.md) are available to use in the new workspace (My Application Extended). This allows you to create reusable parts of your architecture model across different workspaces.\n",
      "\n",
      "CPU times: user 318 ms, sys: 31.8 ms, total: 349 ms\n",
      "Wall time: 1min 12s\n"
     ]
    }
   ],
   "source": [
    "%%time\n",
    "\n",
    "for chunk in chain.stream(\"workspace\"):\n",
    "    print(chunk, end=\"\", flush=True)\n",
    "\n",
    "print(\"\\n\", flush=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "4e0a9e0e-9735-4094-8528-c2764fb50380",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " In this context, the syntax for defining a software system using Structurizr DSL (Domain Specific Language) is as follows:\n",
      "\n",
      "```\n",
      "softwareSystem <name> [description] [tags] {\n",
      "    // Define containers, components, etc. within the braces ({ ... })\n",
      "}\n",
      "```\n",
      "\n",
      "For example:\n",
      "\n",
      "```\n",
      "softwareSystem \"Software System\" {\n",
      "    api = container \"API\"\n",
      "}\n",
      "\n",
      "softwareSystem2 = softwareSystem \"Software System 2\" {\n",
      "        api = container \"API\"\n",
      "}\n",
      "```\n",
      "\n",
      "Here, `<name>` is the name of your software system. The square brackets `[]` indicate that providing a description or tags for the software system is optional. Containers, components, and other elements can be defined within the curly braces `{}`.\n",
      "\n",
      "CPU times: user 225 ms, sys: 31.5 ms, total: 256 ms\n",
      "Wall time: 53 s\n"
     ]
    }
   ],
   "source": [
    "%%time\n",
    "\n",
    "for chunk in chain.stream(\"what is the syntax of softwareSystem\"):\n",
    "    print(chunk, end=\"\", flush=True)\n",
    "\n",
    "print(\"\\n\", flush=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "65d736ae-485e-4fd3-9dca-cb441b550099",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " The syntax for a `workspace` in Structurizr DSL is as follows:\n",
      "\n",
      "```\n",
      "workspace [name] [description] {\n",
      "    // Model definitions go here\n",
      "}\n",
      "```\n",
      "\n",
      "The optional `name` and `description` provide additional information about the workspace. Within the `{ ... }`, you define your model using various language constructs such as `softwareSystem`, `person`, or `container`. For example:\n",
      "\n",
      "```\n",
      "workspace \"My Workspace\" {\n",
      "    softwareSystem1 = softwareSystem \"Software System 1\" {\n",
      "        api = container \"API\"\n",
      "    }\n",
      "}\n",
      "```\n",
      "\n",
      "More complex models can be defined by nesting these constructs. Additional rules and examples can be found in the Structurizr DSL documentation.\n",
      "\n",
      "CPU times: user 200 ms, sys: 42.3 ms, total: 242 ms\n",
      "Wall time: 50.7 s\n"
     ]
    }
   ],
   "source": [
    "%%time\n",
    "\n",
    "for chunk in chain.stream(\"what is the syntax of workspace\"):\n",
    "    print(chunk, end=\"\", flush=True)\n",
    "\n",
    "print(\"\\n\", flush=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a6219907-5333-41a6-8a35-cd9f8ed0299a",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
